js计算价格、金额时精度丢失问题

您所在的位置:网站首页 js 金额 js计算价格、金额时精度丢失问题

js计算价格、金额时精度丢失问题

2023-07-15 17:29| 来源: 网络整理| 查看: 265

最近在做项目的时候,出现了js计算时精度丢失的问题:

通过计算两个数相加来判断结果是否相等(0.1+0.2===0.3 // false); 涉及到金额的计算(保留小数点后两位)。刚开始的时候将金额的精度到分来进行计算;即:金额*100(17.56 * 100 = 1755.9999999999998) 浮点数运算后的精度问题 // 加法 ===================== 0.1 + 0.2 = 0.30000000000000004 0.7 + 0.1 = 0.7999999999999999 0.2 + 0.4 = 0.6000000000000001 // 减法 ===================== 1.5 - 1.2 = 0.30000000000000004 0.3 - 0.2 = 0.09999999999999998 // 乘法 ===================== 17.56 * 100 = 1755.9999999999998 0.8 * 3 = 2.4000000000000004 35.41 * 100 = 3540.9999999999995 // 除法 ===================== 0.3 / 0.1 = 2.9999999999999996 0.69 / 10 = 0.06899999999999999 复制代码

从上面可以看到,0.1+0.2!==0.3并且0.1+0.2的结果是0.30000000000000004。为什么会出现这样的结果呢?

我们一块来探索一下这背后的原因

十进制和二进制表示的小数特点:

日常生活中人类使用的是十进制,如果使用以10为底的质因数,则可以精确表示分数。

2和5是10的质因数。 1/2、1/4、1/5 (0.2)、1/8 和 1/10 (0.1) 可以精确表示,因为分母使用 10 的质因数。 而 1/3、1/6 和 1/7 是重复小数,因为分母使用 3 或 7 的质因数。

另一方面,在计算机使用的是二进制,如果使用以 2为底的素因子,则可以精确表示分数。

2 是 2 的唯一质因数。 所以 1/2、1/4、1/8 都可以精确表示,因为分母使用 2 的质因数。 而 1/5 (0.2) 或 1/10 (0.1) 是重复小数。

我们在计算数学问题的时候,使用的是十进制,计算0.1 + 0.2的结果等于0.3,没有任何问题。但在计算机中,存储数据使用的是二进制,数据由0和1组成。所以在对数据进行计算时,需要将数据全部转换成二进制,再进行数据计算。

那么JavaScript在计算0.1+0.2时到底发生了什么呢?首先,十进制的0.1和0.2会被转换成二进制的,但是由于浮点数用二进制表示时是无穷的:

0.1 -> 0.0001 1001 1001 1001...(1100循环) 0.2 -> 0.0011 0011 0011 0011...(0011循环) 复制代码

IEEE 754 标准的 64 位双精度浮点数的小数部分最多支持53位二进制位,所以两者相加之后得到二进制为:

0.00011001100110011001100110011001100110011001100110011010 +0.00110011001100110011001100110011001100110011001100110100 ------------------------------------------------------------ =0.01001100110011001100110011001100110011001100110011001110 复制代码

因浮点数小数位的限制而截断的二进制数字,再转换为十进制,就成了0.30000000000000004。所以js在进行计算时会出现精度丢失问题,从而产生误差。

解决方法: 1、确定精度的情况下--项目中的解决方法: function numbersequal(number){ return Math.round(number * 100) } console.log(numbersequal(17.56)); // 1756 console.log(numbersequal(0.1756)); // 18 复制代码

缺点:精度不确定的情况下会依然会出现缺失的问题

2、转换为整数计算 function add(num1, num2) { //num1 小数位的长度 const num1Digits = (num1.toString().split('.')[1] || '').length; //num2 小数位的长度 const num2Digits = (num2.toString().split('.')[1] || '').length; // 取最大的小数位作为10的指数 const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits)); // 把小数都转为整数然后再计算 return (num1 * baseNum + num2 * baseNum) / baseNum; } console.log(add(0.1 + 0.2)) // 0.3 复制代码 复制代码

把计算数字 提升 10 的N次方 倍 再 除以 10的N次方。N>1。

3、将小数位转换成字符串去除 . 后进行相乘计算 function mathMultiply(arg1, arg2) { let m = 0; let s1 = arg1.toString(); let s2 = arg2.toString(); try { m += s1.split('.')[1].length; // 小数相乘,小数点后个数相加 } catch (e) {} try { m += s2.split('.')[1].length; } catch (e) {} return ( (Number(s1.replace('.', '')) * Number(s2.replace('.', ''))) / Math.pow(10, m) ); } > mathMultiply(0.1, 0.2); // 0.02 > mathMultiply(1, 2); // 2 复制代码 复制代码

2、3的缺点:

JS中的存储都是通过8字节的double浮点类型表示的,因此它并不能准确记录所有数字,它存在一个数值范围 Number.MAX_SAFE_INTEGER为 9007199254740991,而Number.MIN_SAFE_INTEGER为 -9007199254740991,超出这个范围的话JS是无法表示的 虽然范围有限制,但是数值一般都够用 4、使用ES6提供的极小数Number.EPSILON: Number.EPSILON // 2.220446049250313e-16 Number.EPSILON.toFixed(20) // "0.00000000000000022204" 复制代码 复制代码

引入一个极小的量,目的在于为浮点数计算设置一个误差范围,如果误差能够小于Number.EPSILON,我们就可以认为结果是可靠的。

误差检查函数(出自《ES6标准入门》-阮一峰)

function withinErrorMargin (left, right) { return Math.abs(left - right) < Number.EPSILON } console.log(withinErrorMargin(0.1+0.2,0.3)); //true 复制代码 5、类库:

(1)、 Math.js

介绍:功能强大,内置大量函数,体积较大 Github:github.com/josdejong/m…

(2)、decimal.js

为 JavaScript 提供十进制类型的任意精度数值。

GitHub:github.com/MikeMcl/dec…

加法 Decimal.add(1, 2); // 3 Decimal.add(0.1, 0.2) // 0.3 const x = new Decimal(1); const result = x.plus(2); // 3 复制代码 复制代码 减法 Decimal.sub(3, 1); // 2 Decimal.sub(0.3, 0.1) // 0.2 const x = new Decimal(3); const result = x.sub(1); // 2 复制代码 复制代码 乘法 Decimal.mul(3, 2); // 6 Decimal.mul(17.56, 100) // 1756 const x = new Decimal(3); const result = x.mul(2); // 6 复制代码 复制代码 除法 Decimal.div(6, 2); // 3 Decimal.div(17.56, 100) // 0.1756 Decimal.div(17.56, 0) // Infinity const x = new Decimal(6); const result = x.div(2); // 3 复制代码 复制代码

使用除法时候要注意 除数(分母)不能为0。

总结:

在js计算的过程中会出现精度丢失的问题,通过分析发现原因:十进制转二进制时,因浮点数小数位的限制而截断的二进制数字,再转换为十进制,从而产生误差。

注:站在前人的肩膀上,可以前进的更快、更远。当项目中出现大量的js加减乘除计算的时候,建议使用成熟的库,虽然部分函数可能永远不会用到,但是封装的方法还是值得相信的(●'◡'●)。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3